﻿Imports System
Imports System.Net
Imports System.Net.Mail
Imports System.Threading
Imports System.Text
Imports System.Text.RegularExpressions
Imports System.Globalization

''' <summary>
''' Methods to send emails.
''' </summary>
''' <remarks>
''' The configuration is done using application settings:
''' Smtp.Host, Smtp.Port, Smtp.User, Smtp.Password, Smtp.EnableSsl, Smtp.RetryInterval.
''' </remarks>
Public Class MailUtil

#Region "Private constructor"

	Private Sub New()
		' Can't create instance!
	End Sub

#End Region

#Region "Send email"

	''' <summary>
	''' Sends the specified message using the default SMTP settings, using the current thread.
	''' </summary>
	Public Shared Sub Send(ByVal msg As MailMessage)

		Send(msg, False)

	End Sub

	''' <summary>
	''' Sends the specified message using the default SMTP settings. Retries once if failed. Supports async.
	''' </summary>
	Public Shared Sub Send(ByVal msg As MailMessage, ByVal async As Boolean)

		Dim smtp As New SmtpClient()

		Dim defaultHost As String = ConfigUtil.GetConfig("Smtp.Host", String.Empty)
		Dim defaultPort As Integer = ConfigUtil.GetConfig("Smtp.Port", 0)
		Dim defaultUser As String = ConfigUtil.GetConfig("Smtp.User", String.Empty)
		Dim defaultPassword As String = ConfigUtil.GetConfig("Smtp.Password", String.Empty)
		Dim defaultSSL As Boolean = ConfigUtil.GetConfig("Smtp.EnableSsl", False)


		smtp.DeliveryMethod = SmtpDeliveryMethod.Network
		smtp.EnableSsl = defaultSSL

		If Not String.IsNullOrEmpty(defaultHost) Then
			smtp.Host = defaultHost
		End If

		If defaultPort > 0 Then
			smtp.Port = defaultPort
		End If

		If defaultUser.Length > 0 And defaultPassword.Length > 0 Then
			smtp.Credentials = New NetworkCredential(defaultUser, defaultPassword)
		End If

		Send(msg, False, smtp)

	End Sub

	''' <summary>
	''' Sends the specified message using the default SMTP settings. Retries once if failed. Supports async.
	''' </summary>
	Public Shared Sub Send(ByVal msg As MailMessage, ByVal async As Boolean, ByVal smtp As SmtpClient)

		Dim RETRY_INTERVAL As Integer = 2000
		Dim defaultOverride As String = ConfigUtil.GetConfig("Smtp.OverrideEmailTo", String.Empty)

		If defaultOverride.Contains("@") Then

			If msg.To.Count > 0 Then

				Dim sTo As New StringBuilder

				For i As Integer = 0 To msg.To.Count - 1
					sTo.Append(msg.To(i).Address & ",")
				Next

				msg.To.Clear()
				msg.Headers.Add("Original-To", sTo.ToString())

			End If

			If msg.CC.Count > 0 Then

				Dim sCc As New StringBuilder

				For i As Integer = 0 To msg.CC.Count - 1
					sCc.Append(msg.CC(i).Address & ",")
				Next

				msg.CC.Clear()
				msg.Headers.Add("Original-Cc", sCc.ToString())

			End If

			If msg.Bcc.Count > 0 Then

				Dim sBcc As New StringBuilder

				For i As Integer = 0 To msg.Bcc.Count - 1
					sBcc.Append(msg.Bcc(i).Address & ",")
				Next

				msg.Bcc.Clear()
				msg.Headers.Add("Original-Bcc", sBcc.ToString())

			End If

			msg.To.Add(New MailAddress(defaultOverride))

		End If

		Try

			If async Then
				smtp.SendAsync(msg, Nothing)
			Else
				smtp.Send(msg)
			End If

		Catch

			Thread.Sleep(ConfigUtil.GetConfig("Smtp.RetryInterval", RETRY_INTERVAL))
			smtp.Send(msg)

		End Try

	End Sub

#End Region

#Region "Decoding"

	''' <summary>
	''' Decode an email subject, based on the provided Encoding.
	''' </summary>
	Public Shared Function DecodeSubject(ByVal input As String, ByVal enc As Encoding) As String

		If String.IsNullOrEmpty(input) Then
			Return String.Empty
		End If

		Dim chars As Char() = input.ToCharArray()
		Dim bytes As Byte() = New Byte(chars.Length - 1) {}

		Dim j As Integer = 0
		Dim i As Integer = 0

		While i < chars.Length

			If chars(i) = "="c Then
				i += 1
				If chars.Length >= i + 2 AndAlso Byte.TryParse(New String(chars, i, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, bytes(j)) Then
					i += 1
				Else
					j -= 1
				End If
			ElseIf chars(i) = "_"c Then
				bytes(j) = CByte(Microsoft.VisualBasic.AscW(" "c))
			Else
				bytes(j) = CByte(Microsoft.VisualBasic.AscW(chars(i)))
			End If

			i += 1
			j += 1

		End While

		Return New String(enc.GetChars(bytes, 0, j))

	End Function

	''' <summary>
	''' Decode an email subject, detects the Encoding automatically.
	''' </summary>
	Public Shared Function DecodeSubject(ByVal input As String) As String

		Try

			If input = "" OrElse input Is Nothing Then
				Return ""
			End If

			Dim regex As New Regex("=\?(?<Encoding>[^\?]+)\?(?<Method>[^\?]+)\?(?<Text>[^\?]+)\?=")
			Dim matches As MatchCollection = regex.Matches(input)
			Dim ret As String = input

			If matches.Count > 1 Then
				ret = ret.Replace("?= =?", "?==?")
			End If

			For Each match As Match In matches

				Dim encoding__1 As String = match.Groups("Encoding").Value

				Select Case encoding__1.ToLower()
					Case "utf8"
						encoding__1 = "UTF-8"
						Exit Select
					Case "utf7"
						encoding__1 = "UTF-7"
						Exit Select
				End Select

				Dim method As String = match.Groups("Method").Value
				Dim text As String = match.Groups("Text").Value
				Dim decoded As String

				If method = "B" Then
					Dim bytes As Byte() = Convert.FromBase64String(text)
					Dim enc As Encoding = Encoding.GetEncoding(encoding__1)
					decoded = enc.GetString(bytes)
				Else
					decoded = DecodeSubject(text, Encoding.GetEncoding(encoding__1))
				End If

				ret = ret.Replace(match.Groups(0).Value, decoded)

			Next

			Return ret

		Catch generatedExceptionName As Exception

			Return String.Empty

		End Try

	End Function

#End Region

End Class